/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.util;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.apache.catalina.Globals;
import org.apache.catalina.JmxEnabled;
import org.apache.catalina.LifecycleException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;

/**
 * 当前类代表一个MBean, 如果继承了该类, 那么会被当做一个MBean对待
 * 需要实现getDomainInternal()和getObjectNameKeyProperties()方法
 * 以供MBeanServer得知但概念MBean的ObjectName信息
 *
 * --------------------------------------------------------------------------------------
 * 个人理解可以像作者一样创建一个全局的注册对象以维护一个全局唯一的值, 以供注册和维护, 可以做成一种抽象具体业务细节需要实现具体方法
 * 然后调用别的抽象的init方法, 在init方法定义一个hook方法供子类实现, 在hook方法中, 可以将其对象注册进入去, 子类只需要继承当前类
 * 间接继承了初始化类, 从而达到hook方法扩展, 子类无需关心注册问题
 * --------------------------------------------------------------------------------------
 *
 * 主要是针对JMX的一系列初始化操作和MBean的维护
 * Registry 用来维护获取MBeanServer, 想其MBeanServer中注册当前MBean
 */
public abstract class LifecycleMBeanBase extends LifecycleBase
        implements JmxEnabled {

    private static final Log log = LogFactory.getLog(LifecycleMBeanBase.class);

    private static final StringManager sm =
        StringManager.getManager("org.apache.catalina.util");


    /* Cache components of the MBean registration. */
    private String domain = null;

    /**
     * oname就是JMX供外界查看的name, 但是其有规范
     * 项目名[:项目名]:name=MBean真实名称
     */
    private ObjectName oname = null;
    /**
     * 每一个组件每一个容器甚至可以说每一个对象都可以创建其共外界访问的MBean
     * Tomcat作者的思想可能是组件可操作,可透视化
     * 如: Engine Host Server等
     * 如果一个组件想被外界操作, 继承LifecycleMBeanBase 创建一个MBean代表此组件即可
     * mserver全局只有一个 所有MBean有共同的MBeanServer 因为他永远从MBeanServer中获取第一个
     */
    @Deprecated
    protected MBeanServer mserver = null;

    /**
     * Sub-classes wishing to perform additional initialization should override
     * this method, ensuring that super.initInternal() is the first call in the
     * overriding method.
     * 想要执行更多的初始化操作,应当重写此方法, 但是确保子类首先执行父类的initInternal()方法
     * 服务生命周期编程initializing时,会调用此方法
     * 如:
     * 服务调用init()方法会进行状态的判断与更新后hook此方法
     */
    @Override
    protected void initInternal() throws LifecycleException {
        // If oname is not null then registration has already happened via
        // preRegister().
        if (oname == null) {
            // 获取Registry的单例对象 getRegistry()类似, newInstance(), 只不过是加了安全校验, 传入两个null则是不做安全校验
            mserver = Registry.getRegistry(null, null)
                    // 获取一个MBeanServer有则直接返回, 有多个则返回第一个, 没有则创建
                    // MBeanServer提供外界访问MBean对象的对象信息以及操作接口
                    // MBean则是服务中一个可以被外界操作和修改的java对象
                    // 详情请看JMX的使用
                    .getMBeanServer();

            // 创建当前组件的ObjectName
            // Server Service Engine Host都是一个MBean, 都有oname
            oname = register(this, getObjectNameKeyProperties());
        }
    }


    /**
     * Sub-classes wishing to perform additional clean-up should override this
     * method, ensuring that super.destroyInternal() is the last call in the
     * overriding method.
     */
    @Override
    protected void destroyInternal() throws LifecycleException {
        unregister(oname);
    }


    /**
     * Specify the domain under which this component should be registered. Used
     * with components that cannot (easily) navigate the component hierarchy to
     * determine the correct domain to use.
     */
    @Override
    public final void setDomain(String domain) {
        this.domain = domain;
    }


    /**
     * Obtain the domain under which this component will be / has been
     * registered.
     */
    @Override
    public final String getDomain() {
        if (domain == null) {
            domain = getDomainInternal();
        }

        if (domain == null) {
            domain = Globals.DEFAULT_MBEAN_DOMAIN;
        }

        return domain;
    }


    /**
     * Method implemented by sub-classes to identify the domain in which MBeans
     * should be registered.
     *
     * @return  The name of the domain to use to register MBeans.
     */
    protected abstract String getDomainInternal();


    /**
     * Obtain the name under which this component has been registered with JMX.
     */
    @Override
    public final ObjectName getObjectName() {
        return oname;
    }


    /**
     * Allow sub-classes to specify the key properties component of the
     * {@link ObjectName} that will be used to register this component.
     *
     * @return  The string representation of the key properties component of the
     *          desired {@link ObjectName}
     */
    protected abstract String getObjectNameKeyProperties();


    /**
     * Utility method to enable sub-classes to easily register additional
     * components that don't implement {@link JmxEnabled} with an MBean server.
     * <br>
     * Note: This method should only be used once {@link #initInternal()} has
     * been called and before {@link #destroyInternal()} has been called.
     *
     * @param obj                       The object the register
     * @param objectNameKeyProperties   The key properties component of the
     *                                  object name to use to register the
     *                                  object
     *
     * @return  The name used to register the object
     */
    protected final ObjectName register(Object obj,
            String objectNameKeyProperties) {

        // Construct an object name with the right domain
        StringBuilder name = new StringBuilder(
                // 子类通过继承并实现当前类的getDomainInternal()方法定义自己的Domain名称, 否则按照默认的"Catalina"作为Domain
                getDomain());
        name.append(':');
        name.append(objectNameKeyProperties);

        ObjectName on = null;

        try {
            // 根据指定的name定义一个ObjectName, 一个ObjectName在JMX看到的就是一个树状节点
            on = new ObjectName(name.toString());
            // 获取Registry的单例
            Registry.getRegistry(null, null)
                    // 注册当前MBean(全局)
                    .registerComponent(obj, on, null);
        } catch (Exception e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e);
        }

        return on;
    }


    /**
     * Utility method to enable sub-classes to easily unregister additional
     * components that don't implement {@link JmxEnabled} with an MBean server.
     * <br>
     * Note: This method should only be used once {@link #initInternal()} has
     * been called and before {@link #destroyInternal()} has been called.
     *
     * @param objectNameKeyProperties   The key properties component of the
     *                                  object name to use to unregister the
     *                                  object
     */
    protected final void unregister(String objectNameKeyProperties) {
        // Construct an object name with the right domain
        StringBuilder name = new StringBuilder(getDomain());
        name.append(':');
        name.append(objectNameKeyProperties);
        Registry.getRegistry(null, null).unregisterComponent(name.toString());
    }


    /**
     * Utility method to enable sub-classes to easily unregister additional
     * components that don't implement {@link JmxEnabled} with an MBean server.
     * <br>
     * Note: This method should only be used once {@link #initInternal()} has
     * been called and before {@link #destroyInternal()} has been called.
     *
     * @param on    The name of the component to unregister
     */
    protected final void unregister(ObjectName on) {
        Registry.getRegistry(null, null).unregisterComponent(on);
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void postDeregister() {
        // NOOP
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void postRegister(Boolean registrationDone) {
        // NOOP
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void preDeregister() throws Exception {
        // NOOP
    }


    /**
     * Allows the object to be registered with an alternative
     * {@link MBeanServer} and/or {@link ObjectName}.
     */
    @Override
    public final ObjectName preRegister(MBeanServer server, ObjectName name)
            throws Exception {

        this.mserver = server;
        this.oname = name;
        this.domain = name.getDomain().intern();

        return oname;
    }

}
